<?php
/**
 * OK 接口类
 * Author zengdeshun
 * 2018-6-5 15:15:07
 */

namespace platform\driver\okex;

use Log;
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Lib\Timer;
use platform\driver\Driver;
use exception\Platform as PlatformException;
use exception\System as SystemException;

class Okex extends Driver
{
    /**
     * rest api接口地址
     * @var string
     */
    private $api = '';

    /**
     * webSocket接口地址
     * @var string
     */
    private $stream = '';

    /**
     * API 访问密钥
     * @var string
     */
    private $accessKey = '';

    /**
     * 签名密钥
     * @var string
     */
    private $secretKey = '';

    /**
     * Okex接口配置
     * @var array
     */
    private $config = [];

    public function __construct($param = [])
    {

        $this->config = file_get_contents(dirname(__FILE__) . '/config.json');

        $this->config = json_decode($this->config, true);
        $this->api = $this->config['api'];

        $this->stream = $this->config['stream'];

        if (isset($param['access_key']) && isset($param['secret_key'])) {
            $this->accessKey = $param['access_key'];
            $this->secretKey = $param['secret_key'];
        } else {
            $this->accessKey = $this->config['access_key'];
            $this->secretKey = $this->config['secret_key'];
        }

    }

    /**
     * 获取Okex支持的所有币种
     * @return mixed
     */
    public function symbols()
    {
        return $this->config['symbols'];
    }

    /**
     * 获取用户资产
     * @return array
     * @throws \Exception
     */
    public function balance()
    {
        $path  = '/api/v1/userinfo.do';
        $param = [
            'api_key' => $this->accessKey
        ];

        $param = $this->createSign($param);
        $data = curl($this->api . $path, $param, 'POST', ["User-Agent: OKCoinPHP/v1", 'application/x-www-form-urlencoded']);
        return json_decode($data, true);
    }

    /**
     * 下单交易
     * @param $type 买卖类型：限价单(buy/sell) 市价单(buy_market/sell_market)
     * @param $symbol
     * @param $price  下单价格 市价卖单不传price
     * @param $amount 交易数量 市价买单不传amount
     * @return array
     * @throws \Exception
     */
    public function buy(...$data)
    {
        list($symbol, $amount, $price, $type) = $data;

        $path  = '/api/v1/trade.do';
        $param = [
            'api_key' => $this->accessKey,
            'symbol'  => $symbol,
            'type'    => $type
        ];
        if (!empty($price)) {
            $param['price'] = floatval($price);
        }
        if (!empty($amount)) {
            $param['amount'] = floatval($amount);
        }
            $param = $this->createSign($param);
            $result = curl($this->api . $path, $param, 'POST', ["User-Agent: OKCoinPHP/v1", 'application/x-www-form-urlencoded']);
            $result = json_decode($result, true);
            if(isset($result['error_code'])){
                if(array_key_exists($result['error_code'],$this->_errorCode)){
                   $errorCode = $this->_errorCode[$result['error_code']];
                   exception(PlatformException::getErrorMsg($errorCode), $errorCode,  'exception\Platform');
                   Log::record('okex下单失败，错误码'.$errorCode.'错误信息'.$errorCode);
                }
            }
            if ($result['result'] != 'true') {
                exception(PlatformException::getErrorMsg(PlatformException::E_NO_BALANCE), PlatformException::E_NO_BALANCE, 'exception\Platform');
            }
            return $result['order_id'];
    }

    /**
     * 撤销订单
     * @param $orderId
     * @param $symbol
     * @return array
     * @throws \Exception
     */
    public function cancel(...$data)
    {
        list($orderId, $symbol) = $data;
        $path  = '/api/v1/cancel_order.do';
        $param = [
            'api_key'  => $this->accessKey,
            'order_id' => $orderId,
            'symbol'   => $symbol
        ];
        $param = $this->createSign($param);
        $data = curl($this->api . $path, $param, 'POST', ["User-Agent: OKCoinPHP/v1", 'application/x-www-form-urlencoded']);
        if (!is_array($data)) {
            $data = json_decode($data,true);
        }
        if(isset($data['error_code'])) {
            if (array_key_exists($data['error_code'], $this->_errorCode)) {
                $errorCode = $this->_errorCode[$data['error_code']];
                exception(PlatformException::getErrorMsg($errorCode), $errorCode, 'exception\Platform');
                Log::record('okex下单失败，错误码' . $errorCode . '错误信息' . $errorCode);
            }
        }
        if ($data['result'] == false ) {
            exception(PlatformException::getErrorMsg(PlatformException::E_CANCEL_ERROR, 'OK'), PlatformException::E_CANCEL_ERROR, 'exception\Platform');
        }
        return $data;
    }

    /**
     * 获取用户的订单信息
     * @param $orderId
     * @param $symbol
     * @return array
     * @throws \Exception
     */
    public function order(...$data)
    {
        list($orderId, $symbol) = $data;
        $path  = '/api/v1/order_info.do';
        $param = [
            'api_key'  => $this->accessKey,
            'order_id' => $orderId,
            'symbol'   => $symbol
        ];

        $param = $this->createSign($param);
        $result = curl($this->api . $path, $param, 'POST', ["User-Agent: OKCoinPHP/v1", 'application/x-www-form-urlencoded']);
        $result = json_decode($result,true);
        if ($result['result'] != 'true') {
            exception(PlatformException::getErrorMsg(PlatformException::E_GET_ORDER_ERROR), PlatformException::E_GET_ORDER_ERROR, 'exception\Platform');
        }
        return $result['orders'];
    }

    /**
     * 参数签名
     * @param $params
     * @return mixed
     */
    public function createSign($params)
    {
        ksort($params);
        $sign = "";
        while ($key = key($params)) {
            $sign .= $key . "=" . $params[$key] . "&";
            next($params);
        }
        $sign = $sign . "secret_key=" . $this->secretKey;
        $sign = strtoupper(md5($sign));
        $params['sign'] = $sign;
        return $params;
    }

    /**
     * webSocket订阅ticker数据
     * @param $symbols
     * @param $callback
     * @return bool
     */
    public function tickerWebSocket($symbols, $callback)
    {
        $param = [];
        foreach ($symbols as $symbol => $value) {
            $param[] = [
                'event'   => 'addChannel',
                'channel' => "ok_sub_spot_{$symbol}_ticker"
            ];
        }

        $this->webSocketClient($param, $callback);
        return true;
    }

    /**
     * webSocket接口统一请求客户端
     * @param $param
     * @param $callback
     * @return bool
     */
    protected function webSocketClient($param, $callback)
    {
        try {
            $con = new AsyncTcpConnection($this->stream);
        } catch (\Exception $e) {
            Log::record('请求webSocket接口出错 ' . $this->stream . $e->getMessage());
        }
        $con->transport = 'ssl'; // 设置以ssl加密方式访问，使之成为wss
        $con->onConnect = function($con) use ($param) {
            if (!empty($param)) {
                if (is_array($param)) {
                    foreach ($param as $value) {
                        $con->send(json_encode($value));
                    }
                } else {
                    $con->send(json_encode($param));
                }
            }
            //设置定时器向服务器发送心跳数据
            $con->timerId = Timer::add(30, function() use ($con) {
                $heartBeatData = json_encode(['event' => 'ping']);
                $con->send($heartBeatData);
            });
        };

        $con->onMessage = function($con, $data) use ($callback) {
            $data = json_decode(gzinflate($data), true);
            if (!isset($data['event'])) {
                call_user_func_array($callback, array($data));
            }
        };

        $con->connect();
        return true;
    }

    /**
     * 订阅depth数据[订阅10条]
     * @param $symbols
     * @param $callback
     * @return bool
     */
    public function depth(array $symbols, callable $callback): bool
    {
        $param = [];
        foreach ($symbols as $symbol => $value) {
            $param[] = [
                'event'   => 'addChannel',
                'channel' => "ok_sub_spot_{$symbol}_depth_10"
            ];
        }

        $this->webSocketClient($param, $callback);
        return true;
    }

    /**
     * 订阅合约K线数据[订阅10条]
     * @param $symbols
     * @param $callback
     * @return bool
     */
    public function kline(array $symbols, callable $callback): bool
    {
        $param = [];
        foreach ($symbols as $symbol => $value) {
            $param[] = [
                'event'   => 'addChannel',
                'channel' => "ok_sub_spot_{$symbol}_kline_1min"
            ];
        }

        foreach ($symbols as $symbol => $value) {
            $param[] = [
                'event'   => 'addChannel',
                'channel' => "ok_sub_spot_{$symbol}_kline_5min"
            ];
        }

        foreach ($symbols as $symbol => $value) {
            $param[] = [
                'event'   => 'addChannel',
                'channel' => "ok_sub_spot_{$symbol}_kline_15min"
            ];
        }

        foreach ($symbols as $symbol => $value) {
            $param[] = [
                'event'   => 'addChannel',
                'channel' => "ok_sub_spot_{$symbol}_kline_1hour"
            ];
        }

        $this->webSocketClient($param, $callback);
        return true;
    }

    private $_errorCode = [
        '10009'  =>  '21009',
        '10010'  =>  '21010',
        '10014'  =>  '21011',
        '10016'  =>  '21012',
        '10023'  =>  '21013',
        '10047'  =>  '21014',
        '10049'  =>  '21015',
        '10050'  =>  '21016',
        '10100'  =>  '21017',
        '10101'  =>  '21018',
        '10102'  =>  '21019',
        '10033'  =>  '21020',
        '10034'  =>  '21021',
        '1002'   =>  '21022',
        '1218'   =>  '21023',
        '1003'   =>  '21024',
        '1004'   =>  '21025',
        '1009'   =>  '21026',
        '1013'   =>  '21028',
        '1010'   =>  '21027',
        '1018'   =>  '21029',
        '1019'   =>  '21030',
        '1024'   =>  '21031',
        '1048'   =>  '21032',
        '1050'   =>  '21033',
        '1051'   =>  '21034',
        '1216'   =>  '21035',
        '1217'   =>  '21036',
        '20015'  =>  '21037',
    ];

    /**
     * REST方式获取行情
     * @return array
     */
    public function restTicker($symbol)
    {
        $data = [];
        try {
            $headers = [
                'Content-Type: application/x-www-form-urlencoded',
            ];

            $data = curl($this->api . '/api/v1/ticker.do?symbol=' . $symbol, [], 'GET', $headers);
            $data = json_decode($data, true);
        } catch (\Exception $e) {
            Log::record('初始化OKEx请求行情数据出错' . $e->getMessage());
        }

        return $data;
    }

    /**
     * REST方式获取深度
     * @return array
     */
    public function restDepth($symbol)
    {
        $data = [];
        try {
            $headers = [
                'Content-Type: application/x-www-form-urlencoded',
            ];

            $data = curl($this->api . '/api/v1/depth.do?symbol=' . $symbol, [], 'GET', $headers);
            $data = json_decode($data, true);
        } catch (\Exception $e) {
            Log::record('初始化OKEx请求深度数据出错' . $e->getMessage());
        }

        return $data;
    }
}